/**
 * File: Graph.java
 * Created by: mhaimel
 * Created on: Mar 5, 2010
 * CVS:  $Id: Graph.java 1.0 Mar 5, 2010 2:37:30 PM mhaimel Exp $
 */
package uk.ac.ebi.curtain.model.graph;

import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.model.graph.GraphAccess.ProcessEach;
import uk.ac.ebi.curtain.model.graph.GraphAccess.TouchEach;
import uk.ac.ebi.curtain.model.graph.impl.ArcImpl;
import uk.ac.ebi.curtain.model.graph.impl.NodeImpl;

/**
 * @author mhaimel
 *
 */
public abstract class Graph<N,A> {
	private Log log = LogFactory.getLog(this.getClass());
	
	private final Long nodeSize;
	private final AtomicLong arcCount = new AtomicLong(0);

	public Graph(Long nodeSize) {
		this.nodeSize = nodeSize;
	}
	
	protected Log getLog() {
		return log;
	}
	
	public Graph<N, A> buildNewGraph(Long newNodeSize){
		return newGraph(newNodeSize);
	}
	
	public void forceSetArcCount(Long count){
		arcCount.set(count);
	}
	
	public Arc<N,A> forceCreateArc(Long id, Node<N, A> left, Node<N, A> right, NodeEdge leftNodePos, NodeEdge rightNodePos){
		Arc<N, A> arc = _createNewArc(id, left, right,leftNodePos, rightNodePos);
		return arc;
	}
	
	public Node<N, A> getEmptyNode(Long id){
		Node<N, A> node = createNewNode(id);
		doSetEmptyNode(node);
		return node;
	}
	
	protected Node<N, A> createNewNode(Long id){
		return new NodeImpl<N, A>(id);
	}
	
	public Long getNodeSize() {
		return nodeSize;
	}
	
	public void resetArcs(Long count){
//		arcCount.set(count);
		doResetArcs(count);
	}
	
	public Long getArcSize(){
		return doGetStoredArcsCount();
	}
	
	public Long getArcCount() {
		return arcCount.get();
	}
	
	public Arc<N,A> createArc(Node<N, A> left, Node<N, A> right, NodeEdge leftEdge, NodeEdge rightEdge){
		Arc<N, A> tmpArc = 
//			left.getArcRight(right);  
			NodeEdge.End.equals(leftEdge) ? left.getArcRight(right,rightEdge):left.getArcLeft(right,rightEdge);
		if(null == tmpArc){
			Long nextId = _nextArcId();
			tmpArc = _createNewArc(nextId,left, right, leftEdge, rightEdge);
		}		
		return tmpArc;
	}
	

//	public void removeArcIfAvailable(Arc<N,A> arc) {
//		_removeArc(arc,false);
//		doRemoveArc(arc,false);
////		arcCount.decrementAndGet();
//	}
	
	public void removeArc(Arc<N,A> arc){
		_removeArc(arc);
		doRemoveArc(arc);
		_decrementArcCount();
	}

	protected long _decrementArcCount() {
		return arcCount.decrementAndGet();
	}


	protected void _removeArc(Arc<N, A> arc) {
		arc.detachFromNodes();
	}

	private Arc<N, A> _createNewArc(Long nextId,Node<N, A> left, Node<N, A> right, NodeEdge leftEdge, NodeEdge rightEdge) {
		Arc<N, A> tmpArc = new ArcImpl<N,A>(nextId, left, right, leftEdge,rightEdge);
		_connectArc2Nodes(tmpArc);
		return tmpArc;
	}
	
	protected Arc<N, A> _createNewArc(Long id,Arc<N, A> arc) {
		Arc<N, A> tmpArc = new ArcImpl<N,A>(id, arc);
		return tmpArc;
	}

	protected void _connectArc2Nodes(Arc<N, A> arc) {
		if(NodeEdge.End.equals(arc.onLeftNode()))
			arc.getLeft().registerArcRight(arc);
		else 
			arc.getLeft().registerArcLeft(arc);
			
		if(NodeEdge.End.equals(arc.onRightNode()))
			arc.getRight().registerArcRight(arc);
		else 
			arc.getRight().registerArcLeft(arc);
	}
	
	public void buildArcsIndex(){
		Long arcCount = doBuildArcsIndex();
		forceSetArcCount(arcCount);
	}
	

	public void reindexArcs(){
		Long arcCnt = getArcCount();
		getLog().debug("Reindex " + arcCnt + " Arcs of graph ... ");
		Long arcs = doReindexArcs();
		forceSetArcCount(arcs);
		getLog().debug("Finished reindexing Arcs with new count: " + arcs);
	}
	
	public boolean hasNode(Long id){
		return null != doGetNode(id);
	}
	
	public Node<N, A> getNode(Long id){
		Node<N, A> node = doGetNode(id);
		if(null == node){
			throw new NullPointerException("Node with ID not found: " + id);
		}
		return node;
	}

	public Arc<N, A> getArc(Long id) {
		Arc<N, A> arc = doGetArc(id);
		if(null == arc){
			throw new NullPointerException("Arc with ID not found: " + id);
		}
		return arc;
	}


	private long _nextArcId() {
		return arcCount.getAndIncrement();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Long.valueOf(arcCount.get()).hashCode();
		result = prime * result + nodeSize.hashCode();
		for(long i = 0; i < nodeSize; ++i){
			Node<N, A> a = doGetNode(i);
			result = prime * result + ((null == a)? 0 : a.hashCode());
		}
		return result;
	}


	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Graph other = (Graph) obj;
		if (arcCount == null) {
			if (other.arcCount != null)
				return false;
		} else if (arcCount.get() != other.arcCount.get())
			return false;
		if (nodeSize == null) {
			if (other.nodeSize != null)
				return false;
		} else if (!nodeSize.equals(other.nodeSize))
			return false;
		for(long i = 0; i < nodeSize; ++i){
			Node<N, A> a = doGetNode(i);
			Node<N, A> b = other.doGetNode(i);
			if(a == null){
				if(b != null){
					return false;
				}
			} else if(!a.equals(b)){
				return false;
			}
		}
		return true;
	}

	
	public abstract <T extends ProcessEach<Arc<N, A>>> T processArcs(final T operation);
	public abstract <T extends TouchEach<Arc<N, A>>> T processArcs(final T operation);
	public abstract <T extends TouchEach<Node<N, A>>> T processNodes(final T operation);

	protected abstract void doRemoveArc(Arc<N, A> arc);
	
	protected abstract void doSetNode(Node<N, A> node);
	
	protected abstract void doSetEmptyNode(Node<N, A> node) throws IllegalStateException;

	protected abstract Node<N, A> doGetNode(Long id);

	protected abstract Arc<N, A> doGetArc(Long id);

	protected abstract Long doReindexArcs();
	
	protected abstract Long doBuildArcsIndex();
	
	protected abstract void doResetArcs(Long arcSize);
	
	protected abstract Long doGetArcSize();
	
	protected abstract Long doGetStoredArcsCount();
	
	protected abstract Graph<N, A> newGraph(Long nodeSize);
	
//	public static <N,A> Graph<N, A> getDefault(Long size,Class<N> nClazz, Class<A> aClazz){
//		return (Graph<N, A>) new ConcurrentGraph<N,A>(size);
//	}
//	
//	public static <N,A> Graph<N, A> getDefault(Long size){
////		return (Graph<N, A>) new ConcurrentGraph<N,A>(size);
//		return getDefault(size,new ForkJoinPool(2));
//	}
//	
//	public static <N,A> Graph<N, A> getDefault(Long size, ForkJoinPool pool){
//		return (Graph<N, A>) new ParallelGraph<N,A>(size,pool);
//	}
//	
//	
//	@SuppressWarnings("unchecked")
//	public static <N,A> List<Graph<N, A>> getAll(Long size){
//		return (List) Arrays.asList(new Object[]{
//				new ConcurrentGraph<N,A>(size),
//				new ParallelGraph<N,A>(size,new ForkJoinPool(2))
//		});
//	}

	
}
